// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/mac/mach_port_broker.h"

#include "base/command_line.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"

namespace base {

namespace {
    const char kBootstrapPortName[] = "thisisatest";
}

class MachPortBrokerTest : public testing::Test,
                           public base::PortProvider::Observer {
public:
    MachPortBrokerTest()
        : broker_(kBootstrapPortName)
        , event_(base::WaitableEvent::ResetPolicy::MANUAL,
              base::WaitableEvent::InitialState::NOT_SIGNALED)
        , received_process_(kNullProcessHandle)
    {
        broker_.AddObserver(this);
    }
    ~MachPortBrokerTest() override
    {
        broker_.RemoveObserver(this);
    }

    // Helper function to acquire/release locks and call |PlaceholderForPid()|.
    void AddPlaceholderForPid(base::ProcessHandle pid)
    {
        base::AutoLock lock(broker_.GetLock());
        broker_.AddPlaceholderForPid(pid);
    }

    // Helper function to acquire/release locks and call |FinalizePid()|.
    void FinalizePid(base::ProcessHandle pid,
        mach_port_t task_port)
    {
        base::AutoLock lock(broker_.GetLock());
        broker_.FinalizePid(pid, task_port);
    }

    void WaitForTaskPort()
    {
        event_.Wait();
    }

    // base::PortProvider::Observer:
    void OnReceivedTaskPort(ProcessHandle process) override
    {
        received_process_ = process;
        event_.Signal();
    }

protected:
    MachPortBroker broker_;
    WaitableEvent event_;
    ProcessHandle received_process_;
};

TEST_F(MachPortBrokerTest, Locks)
{
    // Acquire and release the locks.  Nothing bad should happen.
    base::AutoLock lock(broker_.GetLock());
}

TEST_F(MachPortBrokerTest, AddPlaceholderAndFinalize)
{
    // Add a placeholder for PID 1.
    AddPlaceholderForPid(1);
    EXPECT_EQ(0u, broker_.TaskForPid(1));

    // Finalize PID 1.
    FinalizePid(1, 100u);
    EXPECT_EQ(100u, broker_.TaskForPid(1));

    // Should be no entry for PID 2.
    EXPECT_EQ(0u, broker_.TaskForPid(2));
}

TEST_F(MachPortBrokerTest, FinalizeUnknownPid)
{
    // Finalizing an entry for an unknown pid should not add it to the map.
    FinalizePid(1u, 100u);
    EXPECT_EQ(0u, broker_.TaskForPid(1u));
}

MULTIPROCESS_TEST_MAIN(MachPortBrokerTestChild)
{
    CHECK(base::MachPortBroker::ChildSendTaskPortToParent(kBootstrapPortName));
    return 0;
}

TEST_F(MachPortBrokerTest, ReceivePortFromChild)
{
    ASSERT_TRUE(broker_.Init());
    CommandLine command_line(
        base::GetMultiProcessTestChildBaseCommandLine());
    broker_.GetLock().Acquire();
    base::Process test_child_process = base::SpawnMultiProcessTestChild(
        "MachPortBrokerTestChild", command_line, LaunchOptions());
    broker_.AddPlaceholderForPid(test_child_process.Handle());
    broker_.GetLock().Release();

    WaitForTaskPort();
    EXPECT_EQ(test_child_process.Handle(), received_process_);

    int rv = -1;
    ASSERT_TRUE(test_child_process.WaitForExitWithTimeout(
        TestTimeouts::action_timeout(), &rv));
    EXPECT_EQ(0, rv);

    EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL),
        broker_.TaskForPid(test_child_process.Handle()));
}

TEST_F(MachPortBrokerTest, ReceivePortFromChildWithoutAdding)
{
    ASSERT_TRUE(broker_.Init());
    CommandLine command_line(
        base::GetMultiProcessTestChildBaseCommandLine());
    broker_.GetLock().Acquire();
    base::Process test_child_process = base::SpawnMultiProcessTestChild(
        "MachPortBrokerTestChild", command_line, LaunchOptions());
    broker_.GetLock().Release();

    int rv = -1;
    ASSERT_TRUE(test_child_process.WaitForExitWithTimeout(
        TestTimeouts::action_timeout(), &rv));
    EXPECT_EQ(0, rv);

    EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL),
        broker_.TaskForPid(test_child_process.Handle()));
}

} // namespace base
